package com.lw.lwte.common.helper;

import com.beust.jcommander.internal.Maps;
import com.lw.lwte.common.exception.CheckException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 反射工具帮助类
 */
@Slf4j
public final class ReflectionHelper {
    private ReflectionHelper() {
    }

    /**
     * 反射设置值
     *
     * @param object
     * @param method
     * @param args
     * @throws ReflectiveOperationException
     */
    public static void setRefValue(Object object, Method method, Object... args) throws ReflectiveOperationException {
        method.invoke(object, args);
    }

    /**
     * 根据名称筛选方法
     * @param map
     * @param name
     * @return
     */
    public static Method filterMethod(Map<Field, Method> map, String name){
        for(Field field : map.keySet()){
            String methodName = field.getName();
            if(methodName.equals(name)){
                return map.get(field);
            }
        }

        return null;
    }

    /**
     * 获取有SETTER方法的字段及其SETTER方法
     *
     * @param clazz Class对象
     * @return 有setter方法的字段和setter方法
     * @throws IntrospectionException 内省异常
     */
    public static Map<Field, Method> fieldAndSetterMethod(Class clazz) throws IntrospectionException {
        Map<Field, Method> map = new LinkedHashMap<>();
        BeanInfo beanInfo = Introspector.getBeanInfo(clazz, Object.class);
        PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
        for (Field field : clazz.getDeclaredFields()) {
            for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
                if (propertyDescriptor.getName().equals(field.getName()) && propertyDescriptor.getWriteMethod() != null) {
                    map.put(field, propertyDescriptor.getWriteMethod());
                }
            }
        }

        return map;
    }

    public static List<String> getFieldNameList(Class<?> clazz) {
        Set<Field> allFields = getAllFields(clazz);
        return allFields.stream().map(Field::getName).collect(Collectors.toList());
    }

    public static Map<String, Class<?>> getFieldNameTypeMap(Class<?> clazz) {
        Map<String, Class<?>> map = Maps.newLinkedHashMap();
        Set<Field> allFields = getAllFields(clazz);
        allFields.forEach(field -> map.put(field.getName(), field.getType()));
        return map;
    }

    public static <T> Object invokeGetOrIsMethod(T entity, Class<?> clazz, String fieldName) {
        Method getOrIsMethod = getGetOrIsMethod(clazz, fieldName);
        try {
            return getOrIsMethod.invoke(entity);
        } catch (Exception e) {
            throw new CheckException("字段"+fieldName+"调用get或者is方法失败");
        }
    }

    public static Method getGetOrIsMethod(Class<?> clazz, String fieldName) {
        Method[] methods = clazz.getMethods();
        if (ArrayUtils.isNotEmpty(methods)) {
            for (Method method : methods) {
                String methodName = method.getName();
                if (StringUtils.startsWithIgnoreCase(methodName, "get")
                        && method.getParameters().length == 0
                        && StringUtils.substringAfter(methodName, "get").equalsIgnoreCase(fieldName)) {
                    return method;
                }else if (StringUtils.startsWithIgnoreCase(methodName, "is")
                        && method.getParameters().length == 0
                        && StringUtils.substringAfter(methodName, "is").equalsIgnoreCase(fieldName)) {
                    return method;
                }
            }
        }
        throw new CheckException("字段"+ fieldName+"不存在对应的get方法或者is方法");
    }


    private static Set<Field> getAllFields(Class<?> clazz) {
        Field[] allFields = FieldUtils.getAllFields(clazz);
        return new HashSet<>(Arrays.asList(allFields));
    }

    private static Method getGetOrIsMethod(Method[] methods, String fileName) {
        if (ArrayUtils.isNotEmpty(methods)) {
            for (Method method : methods) {
                String methodName = method.getName();
                if (method.getParameters().length == 0
                        && StringUtils.substringAfter(methodName, "get").equalsIgnoreCase(fileName)
                        || StringUtils.substringAfter(methodName, "is").equalsIgnoreCase(fileName)) {
                    return method;
                }
            }
        }
        return null;
    }

    /**
     * 直接设置对象属性值,无视private/protected修饰符,不经过setter函数.
     */
    public static void setFieldValue(final Object object, final String fieldName, final Object value) {
        Field field = getDeclaredField(object, fieldName);
        if (field == null) {
            throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + object + "]");
        }
        makeAccessible(field);
        try {
            field.set(object, value);
        } catch (IllegalAccessException e) {
            log.error("Set field value failed.", e);
        }
    }


    /**
     * 循环向上转型,获取对象的DeclaredField.
     */
    public static Field getDeclaredField(final Object object, final String fieldName) {
        return getDeclaredField(object.getClass(), fieldName);
    }
    /**
     * 循环向上转型,获取类的DeclaredField.
     */
    @SuppressWarnings("unchecked")
    private static Field getDeclaredField(final Class clazz, final String fieldName) {
        for (Class superClass = clazz; superClass != Object.class; superClass = superClass.getSuperclass()) {
            try {
                return superClass.getDeclaredField(fieldName);
            } catch (NoSuchFieldException e) {
                // Field不在当前类定义,继续向上转型
            }
        }
        return null;
    }

    /**
     * 强制转换fileld可访问.
     */
    private static void makeAccessible(Field field) {
        if (!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers())) {
            field.setAccessible(true);
        }
    }

}
